package com.jml.service;

import org.redisson.api.RFuture;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.pubsub.LockPubSub;

import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class Redisson中的Lua脚本 {

    protected long internalLockLeaseTime;
    CommandAsyncExecutor commandExecutor;
    UUID id;
    String name;


    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
        //先判断最外面的那个key时候存在,如果返回0表示key不存在就创建一个hset然后设置过期时间之后再返回.
        //如果hset存在并且里面的key存在,就对value加1并且设置过期时间,表示重入次数
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "return redis.call('pttl', KEYS[1]);",
                //KEYS[1]就是 getName(),就是最外面的那个key可以理解Map的名字
                // ARGV[1]就是  internalLockLeaseTime
                // ARGV[2]就是  getLockName(threadId)-->就是在拼接一个lockName,会把线程ID放进去
                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
    }


    protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                //根据hset的key和里面的第一个key判断是否存在数据,不存在表示没有锁直接返回
                //如果存在取出里面的value,吧value进行-1,如果这个值大于0,把新的value存入redis同时进行续命
                //如果这个值小0就删除这个key表示锁已经释放
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                        "end; " +
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                        "if (counter > 0) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                        "return 0; " +
                        "else " +
                        "redis.call('del', KEYS[1]); " +
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; "+
                        "end; " +
                        "return nil;",
                //KEYS[1]--getName()
                // KEYS[2]--getChannelName()
                // ARGV[1]--LockPubSub.UNLOCK_MESSAGE
                // ARGV[2]--internalLockLeaseTime
                // ARGV[3]--getLockName(threadId)
                Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

    }

    //续命:
    // 创建锁成功后会创建一个定时器,
    //默认是10s续命一次,每次续命30秒,他这个续命,
    //默认是没有续命次数会无限期续命下去.
    protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return 0;",
                Collections.<Object>singletonList(getName()),
                internalLockLeaseTime, getLockName(threadId));
    }

    String getChannelName() {
        return prefixName("redisson_lock__channel", getName());
    }

    public static String prefixName(String prefix, String name) {
        if (name.contains("{")) {
            return prefix + ":" + name;
        }
        return prefix + ":{" + name + "}";
    }


    public String getName() {
        return name;
    }
    protected String getLockName(long threadId) {
        return id + ":" + threadId;
    }
}
